// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Diagnostics.CodeAnalysis;
using Microsoft.DotNet.Cli.Utils;
using Microsoft.TemplateEngine.Abstractions;
using Microsoft.TemplateEngine.Abstractions.PhysicalFileSystem;
using Microsoft.TemplateEngine.Cli.PostActionProcessors;
using Microsoft.TemplateEngine.Utils;

namespace Microsoft.DotNet.Cli.Commands.New.PostActions;

internal class DotnetSlnPostActionProcessor(Func<string, IReadOnlyList<string>, string?, bool?, bool>? addProjToSolutionCallback = null) : PostActionProcessorBase
{
    private readonly Func<string, IReadOnlyList<string>, string?, bool?, bool> _addProjToSolutionCallback = addProjToSolutionCallback ?? DotnetCommandCallbacks.AddProjectsToSolution;

    public override Guid Id => ActionProcessorId;

    internal static Guid ActionProcessorId { get; } = new Guid("D396686C-DE0E-4DE6-906D-291CD29FC5DE");

    internal static IReadOnlyList<string> FindSolutionFilesAtOrAbovePath(IPhysicalFileSystem fileSystem, string outputBasePath)
    {
        return FileFindHelpers.FindFilesAtOrAbovePath(fileSystem, outputBasePath, "*.sln")
            ?? FileFindHelpers.FindFilesAtOrAbovePath(fileSystem, outputBasePath, "*.slnx");
    }

    // The project files to add are a subset of the primary outputs, specifically the primary outputs indicated by the primaryOutputIndexes post action argument (semicolon separated)
    // If any indexes are out of range or non-numeric, this method returns false and projectFiles is set to null.
    internal static bool TryGetProjectFilesToAdd(IPostAction actionConfig, ICreationResult templateCreationResult, string outputBasePath, [NotNullWhen(true)] out IReadOnlyList<string> projectFiles)
    {
        List<string> filesToAdd = [];
        projectFiles = [];

        if (actionConfig.Args != null && actionConfig.Args.TryGetValue("primaryOutputIndexes", out string? projectIndexes))
        {
            foreach (string indexString in projectIndexes.Split([';'], StringSplitOptions.RemoveEmptyEntries))
            {
                if (int.TryParse(indexString.Trim(), out int index))
                {
                    if (templateCreationResult.PrimaryOutputs.Count <= index || index < 0)
                    {
                        return false;
                    }

                    filesToAdd.Add(Path.GetFullPath(templateCreationResult.PrimaryOutputs[index].Path, outputBasePath));
                }
                else
                {
                    return false;
                }
            }

            projectFiles = filesToAdd;
            return true;
        }
        else
        {
            foreach (string pathString in templateCreationResult.PrimaryOutputs.Select(x => x.Path))
            {
                filesToAdd.Add(Path.GetFullPath(pathString, outputBasePath));
            }

            projectFiles = filesToAdd;
            return true;
        }
    }

    protected override bool ProcessInternal(IEngineEnvironmentSettings environment, IPostAction action, ICreationEffects creationEffects, ICreationResult templateCreationResult, string outputBasePath)
    {
        IReadOnlyList<string> nearestSlnFilesFound = FindSolutionFilesAtOrAbovePath(environment.Host.FileSystem, outputBasePath);
        if (nearestSlnFilesFound.Count != 1)
        {
            Reporter.Error.WriteLine(CliCommandStrings.PostAction_AddProjToSln_Error_NoSolutionFile);
            return false;
        }

        IReadOnlyList<string> projectFiles = GetConfiguredFiles(action.Args, creationEffects, "projectFiles", outputBasePath, (path) => Path.GetExtension(path).EndsWith("proj", StringComparison.OrdinalIgnoreCase));
        if (!projectFiles.Any())
        {
            //If the author didn't opt in to the new behavior by specifying "projectFiles", use the old behavior
            if (!TryGetProjectFilesToAdd(action, templateCreationResult, outputBasePath, out projectFiles))
            {
                Reporter.Error.WriteLine(CliCommandStrings.PostAction_AddProjToSln_Error_NoProjectsToAdd);
                return false;
            }
        }
        if (projectFiles.Count == 0)
        {
            Reporter.Error.WriteLine(CliCommandStrings.PostAction_AddProjToSln_Error_NoProjectsToAdd);
            return false;
        }

        string? solutionFolder = GetSolutionFolder(action);
        bool? inRoot = GetInRoot(action);

        if (!string.IsNullOrWhiteSpace(solutionFolder) && inRoot is true)
        {
            Reporter.Error.WriteLine(CliCommandStrings.PostAction_AddProjToSln_Error_BothInRootAndSolutionFolderSpecified);
            return false;
        }

        if (inRoot is true)
        {
            Reporter.Output.WriteLine(string.Format(CliCommandStrings.PostAction_AddProjToSln_InRoot_Running, string.Join(" ", projectFiles), nearestSlnFilesFound[0]));
        }
        else
        {
            Reporter.Output.WriteLine(string.Format(CliCommandStrings.PostAction_AddProjToSln_Running, string.Join(" ", projectFiles), nearestSlnFilesFound[0], solutionFolder));
        }
        return AddProjectsToSolution(nearestSlnFilesFound[0], projectFiles, solutionFolder, inRoot);
    }

    private bool AddProjectsToSolution(string solutionPath, IReadOnlyList<string> projectsToAdd, string? solutionFolder, bool? inRoot)
    {
        try
        {
            bool succeeded = _addProjToSolutionCallback(solutionPath, projectsToAdd, solutionFolder, inRoot);
            if (!succeeded)
            {
                Reporter.Error.WriteLine(CliCommandStrings.PostAction_AddProjToSln_Failed_NoReason);
            }
            else
            {
                Reporter.Output.WriteLine(CliCommandStrings.PostAction_AddProjToSln_Succeeded);
            }
            return succeeded;

        }
        catch (Exception e)
        {
            Reporter.Error.WriteLine(string.Format(CliCommandStrings.PostAction_AddProjToSln_Failed, e.Message));
            return false;
        }
    }

    private static string? GetSolutionFolder(IPostAction actionConfig)
    {
        if (actionConfig.Args != null && actionConfig.Args.TryGetValue("solutionFolder", out string? solutionFolder))
        {
            return solutionFolder;
        }
        return null;
    }

    private static bool? GetInRoot(IPostAction actionConfig)
    {
        if (actionConfig.Args != null && actionConfig.Args.TryGetValue("inRoot", out string? inRoot))
        {
            if (bool.TryParse(inRoot, out bool result))
            {
                return result;
            }
        }
        return null;
    }
}
